/*-------------------------------------------------------------------------
 * 前端程序的日志记录框架
 *
 * Copyright (c) 2018-2022, PostgreSQL Global Development Group
 *
 * src/common/logging.c
 *
 *-------------------------------------------------------------------------
 */

#ifndef FRONTEND
#error "This file is not expected to be compiled for backend code"
#endif

#include "postgres_fe.h"

#include <unistd.h>

#include "common/logging.h"

enum pg_log_level __pg_log_level;

static const char *progname;
static int	log_flags;

static void (*log_pre_callback) (void);
static void (*log_locus_callback) (const char **, uint64 *);

static const char *sgr_error = NULL;
static const char *sgr_warning = NULL;
static const char *sgr_note = NULL;
static const char *sgr_locus = NULL;

#define SGR_ERROR_DEFAULT "01;31"
#define SGR_WARNING_DEFAULT "01;35"
#define SGR_NOTE_DEFAULT "01;36"
#define SGR_LOCUS_DEFAULT "01"

#define ANSI_ESCAPE_FMT "\x1b[%sm"
#define ANSI_ESCAPE_RESET "\x1b[0m"

#ifdef WIN32

#ifndef ENABLE_VIRTUAL_TERMINAL_PROCESSING
#define ENABLE_VIRTUAL_TERMINAL_PROCESSING 0x0004
#endif

/*
 * 尝试在 Windows 上启用 VT100 序列处理以进行着色。
 * 如果当前环境不兼容 VT100 或无法启用此模式，
 * 则返回 false。
 */
static bool enable_vt_processing(void)
{
	/* 检查 stderr */
	HANDLE		hOut = GetStdHandle(STD_ERROR_HANDLE);
	DWORD		dwMode = 0;

	if (hOut == INVALID_HANDLE_VALUE)
		return false;

	/*
	 * 查找当前控制台设置并检查是否已经启用 VT100。
	 */
	if (!GetConsoleMode(hOut, &dwMode))
		return false;
	if ((dwMode & ENABLE_VIRTUAL_TERMINAL_PROCESSING) != 0)
		return true;

	dwMode |= ENABLE_VIRTUAL_TERMINAL_PROCESSING;
	if (!SetConsoleMode(hOut, dwMode))
		return false;
	return true;
}
#endif							/* WIN32 */

/*
 * 此调用应在任何输出发生之前进行。
 */
void pg_logging_init(const char *fc_argv0)
{
	const char *fc_pg_color_env = getenv("PG_COLOR");
	bool		fc_log_color = false;
	bool		fc_color_terminal = isatty(fileno(stderr));

#ifdef WIN32

	/*
	 * 在 Windows 上，如果使用终端，请检查环境是否兼容 VT100。
	 */
	if (color_terminal)
		color_terminal = enable_vt_processing();
#endif

	/* 通常是默认值，但在 Windows 上不是 */
	setvbuf(stderr, NULL, _IONBF, 0);

	progname = get_progname(fc_argv0);
	__pg_log_level = PG_LOG_INFO;

	if (fc_pg_color_env)
	{
		if (strcmp(fc_pg_color_env, "always") == 0 ||
			(strcmp(fc_pg_color_env, "auto") == 0 && fc_color_terminal))
			fc_log_color = true;
	}

	if (fc_log_color)
	{
		const char *fc_pg_colors_env = getenv("PG_COLORS");

		if (fc_pg_colors_env)
		{
			char	   *fc_colors = strdup(fc_pg_colors_env);

			if (fc_colors)
			{
				for (char *fc_token = strtok(fc_colors, ":"); fc_token; fc_token = strtok(NULL, ":"))
				{
					char	   *fc_e = strchr(fc_token, '=');

					if (fc_e)
					{
						char	   *fc_name;
						char	   *fc_value;

						*fc_e = '\0';
						fc_name = fc_token;
						fc_value = fc_e + 1;

						if (strcmp(fc_name, "error") == 0)
							sgr_error = strdup(fc_value);
						if (strcmp(fc_name, "warning") == 0)
							sgr_warning = strdup(fc_value);
						if (strcmp(fc_name, "note") == 0)
							sgr_note = strdup(fc_value);
						if (strcmp(fc_name, "locus") == 0)
							sgr_locus = strdup(fc_value);
					}
				}

				free(fc_colors);
			}
		}
		else
		{
			sgr_error = SGR_ERROR_DEFAULT;
			sgr_warning = SGR_WARNING_DEFAULT;
			sgr_note = SGR_NOTE_DEFAULT;
			sgr_locus = SGR_LOCUS_DEFAULT;
		}
	}
}

/*
 * 更改日志标志。
 */
void pg_logging_config(int fc_new_flags)
{
	log_flags = fc_new_flags;
}

/*
 * pg_logging_init 将默认日志级别设置为 INFO。想要不同默认值的程序
 * 应该在之后立即使用此设置。
 */
void pg_logging_set_level(enum pg_log_level fc_new_level)
{
	__pg_log_level = fc_new_level;
}

/*
 * 像 --verbose 这样的命令行开关应该调用此函数。
 */
void pg_logging_increase_verbosity(void)
{
	/*
	 * 枚举值的选择使得我们必须降低 __pg_log_level
	 * 才能变得更加详细。
	 */
	if (__pg_log_level > PG_LOG_NOTSET + 1)
		__pg_log_level--;
}

void pg_logging_set_pre_callback(void (*fc_cb) (void))
{
	log_pre_callback = fc_cb;
}

void pg_logging_set_locus_callback(void (*fc_cb) (const char **filename, uint64 *lineno))
{
	log_locus_callback = fc_cb;
}

void pg_log_generic(enum pg_log_level fc_level, enum pg_log_part fc_part,
			   const char *pg_restrict fc_fmt,...)
{
	va_list		fc_ap;

	va_start(fc_ap, fc_fmt);
	pg_log_generic_v(fc_level, fc_part, fc_fmt, fc_ap);
	va_end(fc_ap);
}

void pg_log_generic_v(enum pg_log_level fc_level, enum pg_log_part fc_part,
				 const char *pg_restrict fc_fmt, va_list fc_ap)
{
	int			fc_save_errno = errno;
	const char *fc_filename = NULL;
	uint64		fc_lineno = 0;
	va_list		fc_ap2;
	size_t		fc_required_len;
	char	   *fc_buf;

	Assert(progname);
	Assert(fc_level);
	Assert(fc_fmt);
	Assert(fc_fmt[strlen(fc_fmt) - 1] != '\n');

	/* 如果日志级别太低则不执行任何操作。 */
	if (fc_level < __pg_log_level)
		return;

	/*
	 * 在输出到 stderr 之前刷新 stdout，以确保即使 stdout
	 * 被缓冲也能保持同步。
	 */
	fflush(stdout);

	if (log_pre_callback)
		log_pre_callback();

	if (log_locus_callback)
		log_locus_callback(&fc_filename, &fc_lineno);

	fc_fmt = _(fc_fmt);

	if (!(log_flags & PG_LOG_FLAG_TERSE) || fc_filename)
	{
		if (sgr_locus)
			fprintf(stderr, ANSI_ESCAPE_FMT, sgr_locus);
		if (!(log_flags & PG_LOG_FLAG_TERSE))
			fprintf(stderr, "%s:", progname);
		if (fc_filename)
		{
			fprintf(stderr, "%s:", fc_filename);
			if (fc_lineno > 0)
				fprintf(stderr, UINT64_FORMAT ":", fc_lineno);
		}
		fprintf(stderr, " ");
		if (sgr_locus)
			fprintf(stderr, ANSI_ESCAPE_RESET);
	}

	if (!(log_flags & PG_LOG_FLAG_TERSE))
	{
		switch (fc_part)
		{
			case PG_LOG_PRIMARY:
				switch (fc_level)
				{
					case PG_LOG_ERROR:
						if (sgr_error)
							fprintf(stderr, ANSI_ESCAPE_FMT, sgr_error);
						fprintf(stderr, _("error: "));
						if (sgr_error)
							fprintf(stderr, ANSI_ESCAPE_RESET);
						break;
					case PG_LOG_WARNING:
						if (sgr_warning)
							fprintf(stderr, ANSI_ESCAPE_FMT, sgr_warning);
						fprintf(stderr, _("warning: "));
						if (sgr_warning)
							fprintf(stderr, ANSI_ESCAPE_RESET);
						break;
					default:
						break;
				}
				break;
			case PG_LOG_DETAIL:
				if (sgr_note)
					fprintf(stderr, ANSI_ESCAPE_FMT, sgr_note);
				fprintf(stderr, _("detail: "));
				if (sgr_note)
					fprintf(stderr, ANSI_ESCAPE_RESET);
				break;
			case PG_LOG_HINT:
				if (sgr_note)
					fprintf(stderr, ANSI_ESCAPE_FMT, sgr_note);
				fprintf(stderr, _("hint: "));
				if (sgr_note)
					fprintf(stderr, ANSI_ESCAPE_RESET);
				break;
		}
	}

	errno = fc_save_errno;

	va_copy(fc_ap2, fc_ap);
	fc_required_len = vsnprintf(NULL, 0, fc_fmt, fc_ap2) + 1;
	va_end(fc_ap2);

	fc_buf = pg_malloc_extended(fc_required_len, MCXT_ALLOC_NO_OOM);

	errno = fc_save_errno;			/* malloc 可能会更改 errno */

	if (!fc_buf)
	{
		/* 内存问题，只打印我们能打印的内容然后离开这里 */
		vfprintf(stderr, fc_fmt, fc_ap);
		return;
	}

	vsnprintf(fc_buf, fc_required_len, fc_fmt, fc_ap);

	/* 去掉一个换行符，以便 PQerrorMessage() */
	if (fc_required_len >= 2 && fc_buf[fc_required_len - 2] == '\n')
		fc_buf[fc_required_len - 2] = '\0';

	fprintf(stderr, "%s\n", fc_buf);

	free(fc_buf);
}
